package com.icesoft.mock.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.icesoft.core.common.helper.Resp;
import com.icesoft.core.common.util.DateUtils;
import com.icesoft.core.web.helper.http.SafeRequestResult;
import com.icesoft.core.web.model.CryptoPublicKey;
import com.icesoft.mock.base.ISafeRequestMockListener;
import com.icesoft.mock.base.IMockListener;
import com.icesoft.mock.builder.SafeRequestMockBuilder;
import com.icesoft.mock.builder.MockBuilder;
import lombok.Setter;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

public class MockService {
    private MockMvc mockMvc;
    private Map<String, String> globeHeaders = new HashMap<>();
    private Map<String, String> globeParams = new HashMap<>();

    private MockHttpSession session = new MockHttpSession();

    @Setter
    private String globeUserToken;

    public void setGlobeSession(String name, Object value) {
        this.session.putValue(name, value);
    }

    @Setter
    private IMockListener globeMockListener = new ISafeRequestMockListener() {
    };

    public MockService(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }

    public void addGlobeHeader(String name, String value) {
        globeHeaders.put(name, value);
    }

    public void addGlobeParam(String name, String value) {
        globeParams.put(name, value);
    }

    public <T> Resp<T> safeRequest(SafeRequestMockBuilder safeRequestMockBuilder, TypeReference<Resp<T>> responseType) {
        safeRequestMockBuilder.setCryptoKey(getCryptoKey());
        String data = http(safeRequestMockBuilder);
        String json = SafeRequestResult.decryptJson(data, safeRequestMockBuilder.getSign(), safeRequestMockBuilder.getCryptoService());
        ISafeRequestMockListener mockListener = (ISafeRequestMockListener) safeRequestMockBuilder.getMockListener();
        mockListener.afterDecrypt(json);
        Resp<T> resp = toObject(json, responseType);
        mockListener.handleResp(resp);
        return resp;
    }

    public <T> T toObject(String json, TypeReference<T> cls) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT));
        try {
            return mapper.readValue(json, cls);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public <T> Resp<T> http(MockBuilder mockBuilder, TypeReference<Resp<T>> responseType) {
        String json = http(mockBuilder);
        return toObject(json, responseType);
    }

    public String http(MockBuilder mockBuilder) {
        mockBuilder.addHeaders(globeHeaders);
        mockBuilder.addParams(globeParams);
        IMockListener mockListener = mockBuilder.getMockListener();
        if (mockListener == null) {
            mockListener = globeMockListener;
            mockBuilder.mockListener(globeMockListener);
        }
        if (globeUserToken != null) {
            if (mockBuilder instanceof SafeRequestMockBuilder) {
                ((SafeRequestMockBuilder) mockBuilder).userToken(globeUserToken);
            }
        }
        mockListener.beforeBuild(mockBuilder);

        MockHttpServletRequestBuilder builder = mockBuilder.build();
        builder.session(session);
        if (mockBuilder.getSession() != null) {
            builder.session(mockBuilder.getSession());
        }

        if (mockBuilder instanceof SafeRequestMockBuilder) {
            ISafeRequestMockListener listener = (ISafeRequestMockListener) mockListener;
            listener.beforeHttp((SafeRequestMockBuilder) mockBuilder, builder);
        } else {
            mockListener.beforeHttp(mockBuilder, builder);
        }
        String result = http(builder);
        mockListener.afterHttp(result);
        return result;
    }

    private String http(MockHttpServletRequestBuilder builder) {
        try {
            ResultActions resultActions = mockMvc.perform(builder.accept(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print());
            return resultActions.andReturn().getResponse().getContentAsString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private CryptoPublicKey getCryptoKey() {
        try {
            String keyJson = mockMvc.perform(MockMvcRequestBuilders.get("/public/crypto/publicKey").accept(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse().getContentAsString();
            return toObject(keyJson, new TypeReference<Resp<CryptoPublicKey>>() {
            }).getData();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
